#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# File: streamlit_app.py
# Time: 2023/8/20
# File-Desp: Streamlit UI for product attribute matching visualization

import os
import sys
import streamlit as st
import pandas as pd
import numpy as np
import json
import matplotlib.pyplot as plt
import seaborn as sns
import plotly.express as px
import plotly.graph_objects as go
from loguru import logger

# 全局变量以在服务启动时预加载模型
logger.info("-------服务启动中，正在预加载模型--------")


# 我们在这个步骤禁用Streamlit的热重载功能来防止模型重复加载
os.environ['STREAMLIT_SERVER_RUN_ON_SAVE'] = 'false'

# 确保只加载一次模型，即使在热重载时
if 'matcher' not in st.session_state:
    # 第一次加载并缓存在会话状态中
    try:
        logger.info("首次加载模型，这可能需要几分钟...")
        from attribute_matching import AttributeMatcher
        st.session_state.matcher = AttributeMatcher()
        logger.info("模型加载完成，缓存到会话状态")
    except Exception as e:
        logger.info(f"模型加载错误: {e}")
        st.error(f"模型加载失败: {e}")
        sys.exit(1)

# 从会话状态获取已加载的matcher
matcher = st.session_state.matcher
logger.info(f"模型加载状态: {'成功' if matcher else '失败'}")

# Set page configuration
st.set_page_config(
    page_title="产品属性匹配系统",
    page_icon="🎨",
    layout="wide",
    initial_sidebar_state="expanded"
)

# Title and description
st.title("🎨 智能产品属性匹配系统")

# 显示初始化状态信息，但只在首次加载时
if matcher.embedding_model and not st.session_state.get('model_status_shown', False):
    st.success("嵌入模型已成功加载，系统准备就绪！")
    st.session_state.model_status_shown = True

# Sidebar
st.sidebar.header("操作面板")
tab_selection = st.sidebar.radio(
    "选择功能模块：",
    ["数据库数据匹配", "示例匹配演示", "关于系统"]
)

# Sample data
sample_product_a = {
    "Root情况": ["未破解Root"],
    "侧键": ["侧键正常"],
    "充电功能": ["充电/快充/无线充电正常"],
    "内存": ["8G+128G"],
    "声音功能": ["声音功能正常"],
    "官方维修情况": ["无官方维修记录"],
    "屏幕传感器功能": ["光线、距离感应正常"],
    "屏幕外观": ["屏幕完美无划痕"],
    "屏幕显示": ["显示完美，无瑕疵"],
    "振动功能": ["振动功能正常"],
    "摄像头功能及维修情况": ["相机画面正常，无维修或更换"],
    "整机维修及受潮情况": ["无维修无受潮"],
    "无线功能": ["wifi/蓝牙/nfc/指南针功能正常"],
    "机器重启情况": ["无重启或卡死情况"],
    "机身弯曲情况": ["机身无弯曲"],
    "机身颜色": ["亮黑色"],
    "生物识别功能": ["指纹/面容/虹膜功能正常"],
    "网络制式": ["全网通"],
    "触摸功能": ["触摸功能正常"],
    "账号": ["账号可退出（请先退出再回收）"],
    "购买渠道": ["大陆国行"],
    "边框背板": ["外壳有划痕/侧键异色"],
    "运行情况": ["正常开机进入桌面"],
    "还原激活功能": ["还原激活正常"],
    "通话功能": ["通话正常"]
}

sample_product_b = {
    "个人账号情况": "华为账号可退出（请先退出再回收）",
    "使用情况": "正常开机进入桌面",
    "容量": "8G+128G",
    "屏幕、电池维修": "屏幕/电池无维修更换【原装机】",
    "屏幕外观": "屏幕完美，无划痕【几乎未使用】",
    "屏幕显示": "显示/显色无任何异常",
    "机身外壳、边框": "外壳有细微使用痕迹【较少使用】"
}

# Function to display attributes table
def display_attributes_table(product_dict, title):
    st.subheader(title)
    
    rows = []
    for key, value in product_dict.items():
        if isinstance(value, list):
            val = value[0] if value else ""
        else:
            val = value
        rows.append({"属性项": key, "属性值": val})
    
    df = pd.DataFrame(rows)
    st.dataframe(df, use_container_width=True)

# Function to visualize matching results
def visualize_matches(matches, product_a, product_b):
    if not matches:
        st.warning("没有找到匹配结果！")
        return
    
    # 添加装饰性标题
    st.markdown("""
    <div style='text-align: center; margin-bottom: 2rem;'>
        <h3>✨ 匹配结果可视化 ✨</h3>
    </div>
    """, unsafe_allow_html=True)
    
    # Create dataframe for matches
    match_data = []
    for src_key, tgt_key, score in matches:
        src_value = product_a[src_key][0] if isinstance(product_a[src_key], list) else product_a[src_key]
        tgt_value = product_b[tgt_key]
        match_data.append({
            "源属性项": src_key,
            "目标属性项": tgt_key,
            "相似度": score,
            "源属性值": src_value,
            "目标属性值": tgt_value
        })
    
    match_df = pd.DataFrame(match_data)
    
    # Display matching results table
    st.subheader("匹配结果")
    st.dataframe(match_df, use_container_width=True)
    
    # Interactive bar chart
    st.subheader("匹配相似度可视化")
    
    fig = px.bar(
        match_df.head(10), 
        y="源属性项",
        x="相似度", 
        text="目标属性项",
        color="相似度",
        color_continuous_scale="Viridis",
        labels={"源属性项": "源产品属性项", "相似度": "匹配相似度"},
        title="前10个最佳属性匹配",
        orientation='h'
    )
    
    fig.update_layout(height=500)
    st.plotly_chart(fig, use_container_width=True)
    
    # Heatmap for matched attributes 
    st.subheader("属性值相似度矩阵")
    
    # Take top 8 matches for better visualization
    top_matches = match_df.head(8)
    
    # Create similarity matrix
    sim_matrix = np.zeros((len(top_matches), len(top_matches)))
    labels = top_matches["源属性项"].tolist()
    
    for i, row1 in enumerate(top_matches.itertuples()):
        for j, row2 in enumerate(top_matches.itertuples()):
            # Calculate similarity between source values
            sim = matcher.calculate_similarity(row1.源属性值, row2.源属性值)
            sim_matrix[i, j] = sim
    
    # Create heatmap
    fig = px.imshow(
        sim_matrix,
        x=labels,
        y=labels,
        color_continuous_scale="Viridis",
        labels=dict(x="属性项", y="属性项", color="相似度"),
        title="属性值之间的相似度矩阵"
    )
    
    fig.update_layout(height=500)
    st.plotly_chart(fig, use_container_width=True)
    
    # Detailed comparison view
    st.subheader("详细属性对比")
    
    selected_match = st.selectbox(
        "选择一个匹配对进行详细查看：",
        options=match_df["源属性项"].tolist(),
        format_func=lambda x: f"{x} → {match_df[match_df['源属性项'] == x].iloc[0]['目标属性项']}"
    )
    
    if selected_match:
        match_row = match_df[match_df["源属性项"] == selected_match].iloc[0]
        
        col1, col2 = st.columns(2)
        
        with col1:
            st.markdown("#### 源属性")
            st.markdown(f"**属性项**: {match_row['源属性项']}")
            st.markdown(f"**属性值**: {match_row['源属性值']}")
            
            # Extract numbers for numeric attributes
            nums = matcher.extract_numbers(match_row['源属性值'])
            if nums:
                st.markdown(f"**提取的数值**: {', '.join(nums)}")
        
        with col2:
            st.markdown("#### 目标属性")
            st.markdown(f"**属性项**: {match_row['目标属性项']}")
            st.markdown(f"**属性值**: {match_row['目标属性值']}")
            
            # Extract numbers for numeric attributes
            nums = matcher.extract_numbers(match_row['目标属性值'])
            if nums:
                st.markdown(f"**提取的数值**: {', '.join(nums)}")
        
        # Show similarity metrics
        st.markdown("#### 相似度分析")
        
        key_sim = matcher.calculate_similarity(match_row['源属性项'], match_row['目标属性项'])
        value_sim = matcher.calculate_similarity(match_row['源属性值'], match_row['目标属性值'])
        
        # Display metrics
        col1, col2, col3 = st.columns(3)
        col1.metric("属性项相似度", f"{key_sim:.2f}")
        col2.metric("属性值相似度", f"{value_sim:.2f}")
        col3.metric("综合相似度", f"{match_row['相似度']:.2f}")
        
        # Method used
        method_used = "自动选择"
        if matcher.extract_numbers(match_row['源属性值']) and matcher.extract_numbers(match_row['目标属性值']):
            method_used = "编辑距离 (适用于数值属性)"
        elif matcher.embedding_model:
            method_used = "语义嵌入 (适用于文本属性)"
        else:
            method_used = "TF-IDF (文本相似度备选方法)"
            
        st.info(f"使用的匹配方法: {method_used}")

# Function to handle custom attribute input
def handle_custom_attributes():
    st.subheader("自定义属性匹配")
    
    # 添加装饰性说明
    st.markdown("""
    <div style='text-align: center; margin-bottom: 2rem;'>
        <p style='font-size: 1.1em;'>🎨 在这里输入自定义属性进行匹配 🎨</p>
    </div>
    """, unsafe_allow_html=True)
    
    col1, col2 = st.columns(2)
    
    with col1:
        st.markdown("### 产品A属性")
        product_a_json = st.text_area(
            "输入产品A的属性 (JSON格式)",
            value=json.dumps(sample_product_a, ensure_ascii=False, indent=2),
            height=300
        )
        
    with col2:
        st.markdown("### 产品B属性")
        product_b_json = st.text_area(
            "输入产品B的属性 (JSON格式)",
            value=json.dumps(sample_product_b, ensure_ascii=False, indent=2),
            height=300
        )
    
    threshold = st.slider(
        "匹配阈值 (0-1之间)",
        min_value=0.0,
        max_value=1.0,
        value=0.6,
        step=0.05,
        help="只有相似度大于该阈值的属性对才会被展示"
    )
    
    if st.button("开始匹配", type="primary"):
        try:
            # Parse JSON
            product_a = json.loads(product_a_json)
            product_b = json.loads(product_b_json)
            
            # Display attribute tables
            col1, col2 = st.columns(2)
            with col1:
                display_attributes_table(product_a, "产品A属性详情")
            with col2:
                display_attributes_table(product_b, "产品B属性详情")
            
            # Match attributes
            with st.spinner("正在匹配属性..."):
                matches = matcher.find_best_attribute_matches(
                    product_a, product_b, threshold=threshold
                )
                
            # Visualize matches
            visualize_matches(matches, product_a, product_b)
            
        except Exception as e:
            st.error(f"发生错误: {str(e)}")
            st.error("请确保输入的JSON格式正确")

# Function to load and display database data
def handle_database_data():
    st.subheader("数据库数据匹配")
    
    # 初始化默认SQL
    if 'ahs_sql' not in st.session_state:
        st.session_state.ahs_sql = """
        SELECT
            CAST(date(partition_date) + interval '1' day as varchar) AS partition_date,
            product_category_name,
            product_brand_name,
            product_name,
            property_name,
            property_value_name
        FROM 
            rpt.rpt_platform_trade_price_compass_compare_product_property
        WHERE 
            date(partition_date) = current_date + interval + '-1' day
        """
    
    if 'zz_sql' not in st.session_state:
        st.session_state.zz_sql = """
        SELECT
            partition_date,
            category_name,
            brand_name,
            product_name,
            property_items_name,
            property_value_name
        FROM 
            dim.dim_external_crawl_product_property_value
        where 
            date(partition_date) = current_date
            and platform_name = 'ZZ'
            and disabled = 0
        """
    
    # 预先加载数据
    if 'ahs_property' not in st.session_state or 'zz_property' not in st.session_state:
        try:
            from data_presto import engine
            
            with st.spinner("正在加载数据..."):
                ahs_property = pd.read_sql(st.session_state.ahs_sql, engine)
                st.session_state.ahs_property = ahs_property
                
                zz_property = pd.read_sql(st.session_state.zz_sql, engine)
                st.session_state.zz_property = zz_property
                
                st.success(f"成功加载数据: AHS ({len(ahs_property)} 行), ZZ ({len(zz_property)} 行)")
                
        except Exception as e:
            st.error(f"加载数据时出错: {str(e)}")
            st.session_state.data_error = True
    
    # SQL编辑器
    st.markdown("### SQL编辑器")
    st.markdown("以下是SQL查询语句，您可以修改它们来获取不同的数据：")
    
    sql_tab1, sql_tab2 = st.tabs(["AHS数据SQL", "ZZ数据SQL"])
    
    with sql_tab1:
        st.session_state.ahs_sql = st.text_area(
            "编辑AHS数据SQL",
            value=st.session_state.ahs_sql,
            height=300,
            key="ahs_sql_editor"
        )
    
    with sql_tab2:
        st.session_state.zz_sql = st.text_area(
            "编辑ZZ数据SQL",
            value=st.session_state.zz_sql,
            height=300,
            key="zz_sql_editor"
        )
    
    # 执行SQL按钮
    if st.button("执行SQL查询"):
        try:
            from data_presto import engine
            
            with st.spinner("正在执行SQL查询..."):
                ahs_property = pd.read_sql(st.session_state.ahs_sql, engine)
                st.session_state.ahs_property = ahs_property
                
                zz_property = pd.read_sql(st.session_state.zz_sql, engine)
                st.session_state.zz_property = zz_property
                
                st.success(f"成功加载数据: AHS ({len(ahs_property)} 行), ZZ ({len(zz_property)} 行)")
                
        except Exception as e:
            st.error(f"执行SQL查询时出错: {str(e)}")
            return
    
    # 检查数据是否加载
    if 'ahs_property' not in st.session_state or 'zz_property' not in st.session_state:
        if 'data_error' in st.session_state and st.session_state.data_error:
            st.warning("无法加载初始数据，请修改SQL查询并重新执行")
        else:
            st.warning("请执行SQL查询以加载数据")
        return
    
    # 数据预览
    if st.checkbox("显示数据预览", value=True):
        tab1, tab2 = st.tabs(["AHS数据", "ZZ数据"])
        
        with tab1:
            st.dataframe(st.session_state.ahs_property.head(10), use_container_width=True)
        
        with tab2:
            st.dataframe(st.session_state.zz_property.head(10), use_container_width=True)
    
    # 品类和品牌选择
    st.markdown("### 选择品类和品牌")
    
    ahs_categories = sorted(st.session_state.ahs_property['product_category_name'].unique())
    zz_categories = sorted(st.session_state.zz_property['category_name'].unique())
    
    common_categories = sorted(set(ahs_categories) & set(zz_categories))
    
    if not common_categories:
        st.error("未找到共同的品类，无法进行匹配！")
        return
    
    selected_category = st.selectbox(
        "选择品类*",
        options=common_categories,
        help="必须选择品类才能进行匹配"
    )
    
    ahs_filtered = st.session_state.ahs_property[st.session_state.ahs_property['product_category_name'] == selected_category]
    zz_filtered = st.session_state.zz_property[st.session_state.zz_property['category_name'] == selected_category]
    
    # 获取当前品类下AHS的所有品牌
    all_brands = sorted(ahs_filtered['product_brand_name'].unique())
    
    selected_brand = st.selectbox(
        "选择品牌 (可选)",
        options=["全部"] + all_brands,
        help="可以选择特定品牌进行匹配，或选择'全部'匹配所有品牌"
    )
    
    if selected_brand != "全部":
        ahs_filtered = ahs_filtered[ahs_filtered['product_brand_name'] == selected_brand]
        # 筛选ZZ数据时，使用品牌匹配
        zz_filtered = zz_filtered[zz_filtered['brand_name'] == selected_brand]
    
    # 产品选择
    st.markdown("### 选择要匹配的产品")
    
    ahs_products = sorted(ahs_filtered['product_name'].unique())
    zz_products = sorted(zz_filtered['product_name'].unique())
    
    if not ahs_products or not zz_products:
        st.warning(f"在选定的{'品类' + (f'和品牌 {selected_brand}' if selected_brand != '全部' else '')}下没有找到可匹配的产品！")
        return
    
    # 产品选择
    col1, col2 = st.columns(2)
    
    with col1:
        selected_ahs = st.selectbox(
            "选择AHS产品:",
            options=ahs_products,
            index=0 if len(ahs_products) > 0 else None
        )
    
    with col2:
        selected_zz = st.selectbox(
            "选择ZZ产品:",
            options=zz_products,
            index=0 if len(zz_products) > 0 else None
        )
    
    # 添加匹配按钮到产品选择之后
    if selected_ahs and selected_zz:
        # 准备数据字典
        ahs_attrs = ahs_filtered[ahs_filtered['product_name'] == selected_ahs]
        ahs_dict = {}
        for _, row in ahs_attrs.iterrows():
            ahs_dict[row['property_name']] = [row['property_value_name']]
            
        zz_attrs = zz_filtered[zz_filtered['product_name'] == selected_zz]
        zz_dict = {}
        for _, row in zz_attrs.iterrows():
            zz_dict[row['property_items_name']] = row['property_value_name']
        
        st.markdown("### 执行属性匹配")
        col1, col2, col3 = st.columns([2, 4, 2])
        with col2:
            match_button = st.button("进行详细属性匹配分析", type="primary", use_container_width=True)
            st.caption("点击按钮查看所选产品的详细属性匹配结果")
        
        if match_button:
            with st.spinner("正在处理产品属性..."):
                # 显示属性表格
                col1, col2 = st.columns(2)
                with col1:
                    display_attributes_table(ahs_dict, f"AHS产品: {selected_ahs}")
                with col2:
                    display_attributes_table(zz_dict, f"ZZ产品: {selected_zz}")
                
                # 匹配属性
                threshold = 0.6
                matches = matcher.find_best_attribute_matches(
                    ahs_dict, zz_dict, threshold=threshold
                )
                
                # 可视化匹配结果
                visualize_matches(matches, ahs_dict, zz_dict)
    
    # 产品推荐部分
    if selected_ahs:
        # 获取AHS产品的属性
        if 'ahs_dict' not in locals():
            ahs_attrs = ahs_filtered[ahs_filtered['product_name'] == selected_ahs]
            ahs_dict = {}
            for _, row in ahs_attrs.iterrows():
                ahs_dict[row['property_name']] = [row['property_value_name']]
        
        # 推荐ZZ产品
        st.markdown("### 推荐匹配的ZZ产品")
        recommendations = []
        for zz_product in zz_products:
            zz_attrs = zz_filtered[zz_filtered['product_name'] == zz_product]
            zz_dict = {}
            for _, row in zz_attrs.iterrows():
                zz_dict[row['property_items_name']] = row['property_value_name']
            
            matches = matcher.find_best_attribute_matches(ahs_dict, zz_dict)
            if matches:
                avg_score = sum(score for _, _, score in matches) / len(matches)
                recommendations.append((zz_product, avg_score))
        
        recommendations.sort(key=lambda x: x[1], reverse=True)
        
        if recommendations:
            st.info(f"为AHS产品 '{selected_ahs}' 推荐匹配: {recommendations[0][0]}\n匹配度: {recommendations[0][1]:.2f}")
    
    if selected_zz:
        # 获取ZZ产品的属性
        if 'zz_dict' not in locals():
            zz_attrs = zz_filtered[zz_filtered['product_name'] == selected_zz]
            zz_dict = {}
            for _, row in zz_attrs.iterrows():
                zz_dict[row['property_items_name']] = row['property_value_name']
        
        # 推荐AHS产品
        st.markdown("### 推荐匹配的AHS产品")
        recommendations = []
        for ahs_product in ahs_products:
            ahs_attrs = ahs_filtered[ahs_filtered['product_name'] == ahs_product]
            ahs_dict = {}
            for _, row in ahs_attrs.iterrows():
                ahs_dict[row['property_name']] = [row['property_value_name']]
            
            matches = matcher.find_best_attribute_matches(ahs_dict, zz_dict)
            if matches:
                avg_score = sum(score for _, _, score in matches) / len(matches)
                recommendations.append((ahs_product, avg_score))
        
        recommendations.sort(key=lambda x: x[1], reverse=True)
        
        if recommendations:
            st.info(f"为ZZ产品 '{selected_zz}' 推荐匹配: {recommendations[0][0]}\n匹配度: {recommendations[0][1]:.2f}")

# Function to display system information
def show_about():
    st.subheader("关于系统")
    
    st.markdown("""
    ### 智能产品属性匹配与推荐系统
    
    本系统使用自然语言处理和机器学习技术自动匹配不同来源产品之间的属性项和属性值。
    
    #### 技术实现
    
    系统采用混合策略处理不同类型的属性值：
    
    1. **数字和单位**: 使用正则表达式提取数字和单位，结合编辑距离和Jaccard相似度进行匹配
    2. **纯文本**: 使用预训练的多语言句子嵌入模型，计算余弦相似度
    3. **自适应选择**: 自动分析属性值类型，选择最合适的匹配算法
    4. **中文处理**: 使用jieba分词处理中文文本
    
    #### 运行环境
    
    - Python版本: 3.8+
    - 嵌入模型: {0}
    - 依赖库: pandas, numpy, scikit-learn, sentence-transformers, jieba, fuzzywuzzy, streamlit
    """.format("已加载" if matcher.embedding_model else "未加载"))
    
    # System status
    st.subheader("系统状态")
    
    col1, col2, col3 = st.columns(3)
    
    with col1:
        st.metric("嵌入模型", "已加载" if matcher.embedding_model else "未加载")
    
    with col2:
        # Try to get the jieba status
        jieba_loaded = True
        try:
            import jieba
            jieba.cut("测试")
        except:
            jieba_loaded = False
        st.metric("中文分词", "已加载" if jieba_loaded else "未加载")
    
    with col3:
        # Try to access the data
        db_connected = False
        try:
            from data_presto import ahs_property, zz_property
            if len(ahs_property) > 0 and len(zz_property) > 0:
                db_connected = True
        except:
            pass
        st.metric("数据库连接", "已连接" if db_connected else "未连接")
    
    # Version info
    versions = {
        "streamlit": st.__version__,
        "pandas": pd.__version__,
        "numpy": np.__version__
    }
    
    try:
        import sklearn
        versions["scikit-learn"] = sklearn.__version__
    except:
        versions["scikit-learn"] = "未安装"
    
    try:
        from sentence_transformers import __version__ as st_version
        versions["sentence-transformers"] = st_version
    except:
        versions["sentence-transformers"] = "未安装"
    
    # Display version info
    st.markdown("#### 软件版本")
    
    version_df = pd.DataFrame({
        "库": list(versions.keys()),
        "版本": list(versions.values())
    })
    
    st.dataframe(version_df)
    
# Main function that controls the UI flow
def main():
    # Show content based on tab selection
    if tab_selection == "数据库数据匹配":
        handle_database_data()
    elif tab_selection == "示例匹配演示":
        st.header("示例匹配演示")
        st.markdown("""
        下面展示了两个示例产品之间的属性匹配结果。系统自动识别最相似的属性对，
        并为每对属性计算相似度分数。
        """)
        
        # Display attribute tables
        col1, col2 = st.columns(2)
        with col1:
            display_attributes_table(sample_product_a, "产品A属性详情")
        with col2:
            display_attributes_table(sample_product_b, "产品B属性详情")
        
        # Match attributes and visualize
        with st.spinner("正在匹配属性..."):
            matches = matcher.find_best_attribute_matches(
                sample_product_a, sample_product_b
            )
            
        # Visualize matches
        visualize_matches(matches, sample_product_a, sample_product_b)
    else:  # About
        show_about()
        
# Run the application
if __name__ == "__main__":
    main() 